/*
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <http://www.gnu.org/licenses/>.
 */
package l2s.gameserver.scripts.custom;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledFuture;

import javolution.util.FastList;
import javolution.util.FastMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import l2s.commons.configuration.Config;
import l2s.commons.db.DatabaseFactory;
import l2s.gameserver.Shutdown;
import l2s.gameserver.cache.HtmCache;
import l2s.gameserver.datatables.NpcBufferTable;
import l2s.gameserver.datatables.SkillTable;
import l2s.gameserver.model.L2ItemInstance;
import l2s.gameserver.model.L2Skill;
import l2s.gameserver.model.L2World;
import l2s.gameserver.model.actor.L2Character;
import l2s.gameserver.model.actor.L2Npc;
import l2s.gameserver.model.actor.instance.L2PcInstance;
import l2s.gameserver.model.quest.Quest;
import l2s.gameserver.model.quest.QuestState;
import l2s.gameserver.model.zone.ZoneId;
import l2s.gameserver.network.SystemMessageId;
import l2s.gameserver.network.serverpackets.SystemMessage;
import l2s.gameserver.scripting.ScriptFile;

public class Buffer extends Quest implements ScriptFile
{
	private static final Logger _log = LoggerFactory.getLogger(Buffer.class.getName());
	
	private static String qn = "Buffer";
	
	private final int itemId = Config.ITEM_ID_FOR_PREMIUM_BUFFER;
	
	private static String htmlBase = "data/html-en/mods/buffer/Buffer";
	private String _err = "";
	
	protected final Map<Integer, String> _lastPage;
	protected final Map<Integer, Boolean> _isPetTarget = new FastMap<>();
	protected final Map<Integer, Map<String, BuffProfile>> _buffprofiles = new FastMap<>();
	protected final Map<Integer, Long> _restoreDelays = new FastMap<>();
	
	private class BuffProfile
	{
		protected final List<Integer> _buffs = new FastList<>();
	}
	
	public Buffer()
	{
		super(-1, qn, "custom");
		
		NpcBufferTable.getInstance();
		_lastPage = new FastMap<>();
		Shutdown.getInstance().registerShutdownHandler(saveProfiles);
		Shutdown.getInstance().registerShutdownHandler(stopBuffTask);
	}
	
	private final Runnable saveProfiles = new Runnable()
	{
		@Override
		public void run()
		{
			try (Connection con = DatabaseFactory.getConnection())
			{
				for (int playerId : _buffprofiles.keySet())
				{
					PreparedStatement stm = con.prepareStatement("delete from character_buff_profiles where charId=?");
					stm.setInt(1, playerId);
					stm.execute();
					stm.close();
					
					stm = con.prepareStatement("insert into character_buff_profiles values (?,?,?)");
					stm.setInt(1, playerId);
					Map<String, BuffProfile> profiles = _buffprofiles.get(playerId);
					for (String s : profiles.keySet())
					{
						stm.setString(2, s);
						for (int buffs : profiles.get(s)._buffs)
						{
							stm.setInt(3, buffs);
							stm.execute();
						}
					}
					stm.close();
				}
			}
			catch (SQLException e)
			{
				System.out.println("Buffer: Can't save profiles " + e);
			}
		}
		
	};
	
	private final Runnable stopBuffTask = new Runnable()
	{
		@Override
		public void run()
		{
			try
			{
				if (autoBuffTask != null)
				{
					autoBuffTask.Shutdown();
				}
			}
			catch (Exception e)
			{
				System.out.println("Buffer: Can't stopBuffTask " + e);
			}
		}
	};
	
	@Override
	public String onFirstTalk(L2Npc npc, L2PcInstance player)
	{
		return onTalk(npc, player);
	}
	
	@Override
	public String onTalk(L2Npc npc, L2PcInstance talker)
	{
		if (talker.getQuestState(qn) == null)
		{
			newQuestState(talker);
		}
		getActiveProfile(talker);
		_lastPage.put(talker.getObjectId(), htmlBase + ".htm");
		_isPetTarget.put(talker.getObjectId(), false);
		createBuffProfiles(talker);
		String html = HtmCache.getInstance().getHtm(htmlBase + ".htm");
		return fillHtml(talker, html);
	}
	
	private String ShowLastPage(int playerId)
	{
		if (_lastPage.containsKey(playerId))
		{
			String html = HtmCache.getInstance().getHtm(_lastPage.get(playerId));
			return fillHtml(L2World.getInstance().getPlayer(playerId), html);
		}
		return null;
	}
	
	private static boolean isValidTalker(L2PcInstance player)
	{
		if (checkMagicCondition(player))
		{
			return true;
		}
		return false;
	}
	
	private static boolean checkMagicCondition(L2PcInstance player)
	{
		boolean ok = true;
		
		if ((player.isInJail()) && (Config.BUFFER_RESTRICTION.contains("JAIL")))
		{
			ok = false;
		}
		else if ((player.isInOlympiadMode()) || (player.getOlympiadGameId() >= 0) && (Config.BUFFER_RESTRICTION.contains("OLY")))
		{
			ok = false;
		}
		else if ((player.isInDuel()) && (Config.BUFFER_RESTRICTION.contains("DUEL")))
		{
			ok = false;
		}
		else if ((player.isDead()) && (Config.BUFFER_RESTRICTION.contains("DEAD")))
		{
			ok = false;
		}
		else if ((player.isAlikeDead()) && (Config.BUFFER_RESTRICTION.contains("ALIKE")))
		{
			ok = false;
		}
		else if ((player.isCastingNow()) && (Config.BUFFER_RESTRICTION.contains("CASTING")))
		{
			ok = false;
		}
		else if ((player.isCursedWeaponEquipped()) && (Config.BUFFER_RESTRICTION.contains("CURSED")))
		{
			ok = false;
		}
		else if ((player.inObserverMode()) && (Config.BUFFER_RESTRICTION.contains("OBSERVER")))
		{
			ok = false;
		}
		else if ((player.isFestivalParticipant()) && (Config.BUFFER_RESTRICTION.contains("FESTIVAL")))
		{
			ok = false;
		}
		else if ((player.isIn7sDungeon()) && (Config.BUFFER_RESTRICTION.contains("DUNGEON")))
		{
			ok = false;
		}
		else if ((player.isInParty() && player.getParty().isInDimensionalRift()) && (Config.BUFFER_RESTRICTION.contains("DIMENSION")))
		{
			ok = false;
		}
		else if ((player.isInCombat()) && (Config.BUFFER_RESTRICTION.contains("COMBAT")))
		{
			ok = false;
		}
		else if ((player.getKarma() > 0) && (Config.BUFFER_RESTRICTION.contains("KARMA")))
		{
			ok = false;
		}
		else if ((player.getPvpFlag() > 0) && (Config.BUFFER_RESTRICTION.contains("PVP")))
		{
			ok = false;
		}
		else if (player.isInsideZone(ZoneId.SIEGE) && (Config.BUFFER_RESTRICTION.contains("SIEGE")))
		{
			ok = false;
		}
		else if (player.isInsideZone(ZoneId.NO_SUMMON_FRIEND) && (Config.BUFFER_RESTRICTION.contains("RB")))
		{
			ok = false;
		}
		else if (player.isInsideZone(ZoneId.PVP) && (Config.BUFFER_RESTRICTION.contains("ARENA")))
		{
			ok = false;
		}
		
		return ok;
	}
	
	public boolean restoreCheck(L2PcInstance player)
	{
		Long lastRestore = _restoreDelays.get(player.getObjectId());
		
		if (Config.BUFFER_RESTORE_DELAY <= 0)
		{
			return true;
		}
		
		if (lastRestore == null)
		{
			_restoreDelays.put(player.getObjectId(), System.currentTimeMillis());
			return true;
		}
		
		if ((System.currentTimeMillis() + (Config.BUFFER_RESTORE_DELAY * 1000)) < lastRestore)
		{
			_restoreDelays.put(player.getObjectId(), System.currentTimeMillis());
			return true;
		}
		
		return true;
	}
	
	@SuppressWarnings("synthetic-access")
	@Override
	public String onEvent(String event, QuestState qs)
	{
		try
		{
			L2PcInstance player = qs.getPlayer();
			if (player == null)
			{
				return null;
			}
			
			if ((player.isAlikeDead() || player.isAfraid() || player.isImmobilized()) && !event.startsWith("Chat"))
			{
				return null;
			}
			
			if (!checkMagicCondition(player))
			{
				String htm = htmlBase + "-not.htm";
				String html = HtmCache.getInstance().getHtm(htm);
				return fillHtml(player, html);
			}
			
			L2Character target = player;
			if (_isPetTarget.get(player.getObjectId()) && (player.getPet() != null))
			{
				target = player.getPet();
			}
			
			if (event.startsWith("Chat"))
			{
				if (!checkMagicCondition(player))
				{
					_lastPage.put(player.getObjectId(), htmlBase + "-not.htm");
					return ShowLastPage(player.getObjectId());
				}
				String chatId = "";
				String htm = htmlBase + ".htm";
				if (event.indexOf(" ") != -1)
				{
					chatId = event.substring(event.indexOf(" ") + 1);
				}
				if (!chatId.equals("0"))
				{
					chatId = "-" + chatId;
					if (_isPetTarget.get(player.getObjectId()))
					{
						if (HtmCache.getInstance().pathExists(htmlBase + "-pet" + chatId + ".htm"))
						{
							htm = htmlBase + "-pet" + chatId + ".htm";
						}
						else
						{
							htm = htmlBase + chatId + ".htm";
						}
					}
					else
					{
						htm = htmlBase + chatId + ".htm";
					}
				}
				_lastPage.put(player.getObjectId(), htm);
				String html = HtmCache.getInstance().getHtm(htm);
				return fillHtml(player, html);
			}
			else if (event.startsWith("addpremium"))
			{
				String htm = htmlBase + "-notPremium.htm";
				if (player.getInventory().getItemByItemId(itemId) != null)
				{
					htm = htmlBase + "-Premium.htm";
				}
				else
				{
					player.sendMessage("У вас нет бафф итема.");
				}
				_lastPage.put(player.getObjectId(), htm);
				String html = HtmCache.getInstance().getHtm(htm);
				return fillHtml(player, html);
			}
			else if (event.startsWith("SelectProfile"))
			{
				if (!checkMagicCondition(player))
				{
					_lastPage.put(player.getObjectId(), htmlBase + "-not.htm");
					return ShowLastPage(player.getObjectId());
				}
				String[] args = event.split(" ");
				player.getCharacterData().set("BuffProfile", args[1]);
				String html = HtmCache.getInstance().getHtm(htmlBase + "-p2.htm");
				_lastPage.put(player.getObjectId(), htmlBase + "-p2.htm");
				html = fillHtml(player, html);
				return html;
			}
			else if (event.startsWith("Profile"))
			{
				if (!checkMagicCondition(player))
				{
					_lastPage.put(player.getObjectId(), htmlBase + "-not.htm");
					return ShowLastPage(player.getObjectId());
				}
				String html = HtmCache.getInstance().getHtm(htmlBase + "-p1.htm");
				_lastPage.put(player.getObjectId(), htmlBase + "-p1.htm");
				return fillHtml(player, html);
			}
			else if (event.startsWith("ClearProfile"))
			{
				BuffProfile profile = getActiveProfile(player);
				profile._buffs.clear();
				return onEvent("Profile", qs);
			}
			else if (event.startsWith("DeleteProfile"))
			{
				Map<String, BuffProfile> _profiles = _buffprofiles.get(player.getObjectId());
				if (getActiveProfile(player) != null)
				{
					_profiles.remove(getActiveProfileName(player));
				}
				player.getCharacterData().set("BuffProfile", (String) null);
				return onEvent("Profile", qs);
			}
			else if (event.startsWith("CreateProfile"))
			{
				Map<String, BuffProfile> _profiles = _buffprofiles.get(player.getObjectId());
				if (_profiles == null)
				{
					_buffprofiles.put(player.getObjectId(), new FastMap<String, BuffProfile>());
					_profiles = _buffprofiles.get(player.getObjectId());
				}
				String[] args = event.split(" ");
				if (args.length > 2)
				{
					_err = "�?мя профиля не должно содержать пробелы";
					return onEvent("Profile", qs);
				}
				if (!_profiles.containsKey(args[1]))
				{
					_profiles.put(args[1], new BuffProfile());
				}
				player.getCharacterData().set("BuffProfile", args[1]);
				return onEvent("Profile", qs);
			}
			else if (event.startsWith("UseProfile") && checkMagicCondition(player))
			{
				BuffProfile profile = getActiveProfile(player);
				target = player;
				if (profile != null)
				{
					for (int buff : profile._buffs)
					{
						int[] group = NpcBufferTable.getInstance().getSkillInfo(50000, buff);
						useBuff(buff, group, player, target);
					}
				}
			}
			else if (event.startsWith("RemBuff"))
			{
				if (isValidTalker(player))
				{
					target.stopAllEffects();
				}
			}
			else if (event.startsWith("recAll"))
			{
				if (!checkMagicCondition(player))
				{
					_lastPage.put(player.getObjectId(), htmlBase + "-not.htm");
					return ShowLastPage(player.getObjectId());
				}
				
				if (restoreCheck(player) && isValidTalker(player))
				{
					target.getStatus().setCurrentCpHpMp(target.getMaxCp(), target.getMaxHp(), target.getMaxMp());
					player.sendMessage("CP, HP, MP восстановлены.");
				}
			}
			else if (event.startsWith("Target"))
			{
				_isPetTarget.put(player.getObjectId(), !_isPetTarget.get(player.getObjectId()));
			}
			else if (event.startsWith("Buff"))
			{
				if (!checkMagicCondition(player))
				{
					_lastPage.put(player.getObjectId(), htmlBase + "-not.htm");
					return ShowLastPage(player.getObjectId());
				}
				
				if (!isValidTalker(player))
				{
					return ShowLastPage(player.getObjectId());
				}
				
				String[] buffGroupArray;
				if (event.startsWith("BuffPet"))
				{
					buffGroupArray = event.substring(8).split(" ");
				}
				else
				{
					buffGroupArray = event.substring(5).split(" ");
				}
				
				for (String buffGroupList : buffGroupArray)
				{
					if (buffGroupList == null)
					{
						_log.warn("NPC Buffer Warning: buffer has no buffGroup set in the bypass for the buff selected.");
						return ShowLastPage(player.getObjectId());
					}
					
					int buffGroup = Integer.parseInt(buffGroupList);
					int[] npcBuffGroupInfo = NpcBufferTable.getInstance().getSkillInfo(50000, buffGroup);
					
					if (npcBuffGroupInfo == null)
					{
						_log.warn("NPC Buffer Warning: Player: " + player.getName() + " has tried to use skill group (" + buffGroup + ") not assigned to the NPC Buffer!");
						return ShowLastPage(player.getObjectId());
					}
					if (Config.LIST_BUFFER_PREMIUM_BUFF_ALLOW.contains(buffGroup))
					{
						_lastPage.put(player.getObjectId(), htmlBase + "-Premium.htm");
					}
					useBuff(buffGroup, npcBuffGroupInfo, player, target);
					
				}
			}
			return ShowLastPage(player.getObjectId());
		}
		catch (Exception e)
		{
			return htmlBase + ".htm";
		}
	}
	
	private static String getActiveProfileName(L2PcInstance player)
	{
		try
		{
			return player.getCharacterData().getString("BuffProfile");
		}
		catch (IllegalArgumentException e)
		{
			return "Нет";
		}
		
	}
	
	private BuffProfile getActiveProfile(L2PcInstance player)
	{
		String profileName = null;
		try
		{
			profileName = player.getCharacterData().getString("BuffProfile");
		}
		catch (IllegalArgumentException e)
		{
			return null;
		}
		
		Map<String, BuffProfile> _profiles = _buffprofiles.get(player.getObjectId());
		
		if ((profileName != null) && (_profiles != null))
		{
			return _profiles.get(profileName);
		}
		return null;
	}
	
	private synchronized String fillHtml(L2PcInstance player, String html)
	{
		try
		{
			html = html.replace("%target%", _isPetTarget.get(player.getObjectId()) ? "Слуга" : "Персонаж");
			html = html.replace("%profile%", getActiveProfileName(player));
			html = html.replace("%err%", _err);
			_err = "";
			String profiles = "";
			try
			{
				Map<String, BuffProfile> _profiles = _buffprofiles.get(player.getObjectId());
				if (_profiles != null)
				{
					for (String profileName : _profiles.keySet())
					{
						profiles += "<tr><td><center><a action=\"bypass -h Quest Buffer SelectProfile " + profileName + "\">" + profileName + "</a></center></td></tr>";
					}
				}
				if (profiles.length() == 0)
				{
					profiles = "<tr><td><center>Отсутствуют</center></td></tr>";
				}
				html = html.replace("%profilelist%", profiles);
				BuffProfile profile = getActiveProfile(player);
				if ((profile != null) && (profile._buffs != null) && (profile._buffs.size() > 0))
				{
					html = html.replace("%useprofile%", "<button action=\"bypass -h Quest Buffer UseProfile\" value=\"�?сп. профиль\" width=100 height=21 back=\"sek.cbui94\" fore=\"sek.cbui92\">");
					String buffs = "";
					for (Integer buff : profile._buffs)
					{
						int[] group = NpcBufferTable.getInstance().getSkillInfo(50000, buff);
						buffs += "<tr><td><center>" + SkillTable.getInstance().getInfo(group[0], group[1]).getName() + "</center></td></tr>";
					}
					html = html.replace("%buflist%", buffs);
				}
				else
				{
					html = html.replace("%useprofile%", "");
				}
			}
			catch (Exception e)
			{
				html = html.replace("%useprofile%", "");
				_buffprofiles.remove(player.getObjectId());
				_log.warn("Error getting profiles for " + player.getName() + ", cleaning");
			}
			return html;
		}
		catch (Exception e)
		{
			return "<html><body><br><center>Произошла ошибка при обрашении к баферу, попробуйте позже</center></body></html>";
		}
	}
	
	private boolean useBuff(int grpid, int[] npcBuffGroupInfo, L2PcInstance player, L2Character target)
	{
		for (int i = 0; i < npcBuffGroupInfo.length; i += 4)
		{
			int skillId = npcBuffGroupInfo[i];
			int skillLevel = npcBuffGroupInfo[i + 1];
			int skillFeeId = npcBuffGroupInfo[i + 2];
			int skillFeeAmount = npcBuffGroupInfo[i + 3];
			if (!Config.LIST_BUFFER_BUFF_ALLOW.contains(skillId) && !Config.LIST_BUFFER_PREMIUM_BUFF_ALLOW.contains(skillId))
			{
				player.sendMessage("Недопустимый эффект!");
				return false;
			}
			if (Config.LIST_BUFFER_PREMIUM_BUFF_ALLOW.contains(skillId) && !(player.getInventory().getItemByItemId(itemId) != null))
			{
				player.sendMessage("Недопустимый эффект");
				return false;
			}
			if (skillFeeId != 0)
			{
				L2ItemInstance itemInstance = player.getInventory().getItemByItemId(skillFeeId);
				if ((itemInstance == null) || (!itemInstance.isStackable() && (player.getInventory().getInventoryItemCount(skillFeeId, -1) < skillFeeAmount)))
				{
					SystemMessage sm = new SystemMessage(SystemMessageId.NOT_ENOUGH_ITEMS);
					player.sendPacket(sm);
					return false;
				}
				if (itemInstance.isStackable())
				{
					if (!player.destroyItemByItemId("Npc Buffer", skillFeeId, skillFeeAmount, player.getTarget(), true))
					{
						player.sendPacket(SystemMessageId.NOT_ENOUGH_ITEMS);
						return false;
					}
				}
				else
				{
					for (int k = 0; k < skillFeeAmount; ++k)
					{
						player.destroyItemByItemId("Npc Buffer", skillFeeId, 1, player.getTarget(), true);
					}
				}
			}
			L2Skill skill;
			skill = SkillTable.getInstance().getInfo(skillId, skillLevel);
			
			if (skill != null)
			{
				skill.getEffects(target, target);
				BuffProfile profile = getActiveProfile(player);
				if (profile != null)
				{
					if (!profile._buffs.contains(grpid))
					{
						profile._buffs.add(grpid);
					}
				}
			}
		}
		return true;
	}
	
	@SuppressWarnings("null")
	private Map<String, BuffProfile> createBuffProfiles(L2PcInstance player)
	{
		Map<String, BuffProfile> result;
		result = _buffprofiles.get(player.getObjectId());
		if (result == null)
		{
			result = new FastMap<>();
			_buffprofiles.put(player.getObjectId(), result);
			try (Connection con = DatabaseFactory.getConnection())
			{
				PreparedStatement stm = con.prepareStatement("select * from character_buff_profiles where charId=? order by profileName");
				stm.setInt(1, player.getObjectId());
				ResultSet rs = stm.executeQuery();
				String profileName = "";
				BuffProfile profile = null;
				while (rs.next())
				{
					if (!rs.getString("profileName").equals(profileName))
					{
						profileName = rs.getString("profileName");
						profile = new BuffProfile();
						result.put(profileName, profile);
					}
					profile._buffs.add(rs.getInt("buffGroup"));
				}
				rs.close();
				stm.close();
			}
			catch (SQLException e)
			{
				_log.warn("Buffer: Can't load buf profiles", e);
			}
		}
		
		return result;
	}
	
	public class buffTasks
	{
		private ScheduledFuture<?> buffTask = null;
		
		public void Shutdown()
		{
			stopBuffTask(false);
		}
		
		private void stopBuffTask(final boolean mayInterruptIfRunning)
		{
			if (buffTask != null)
			{
				buffTask.cancel(mayInterruptIfRunning);
				buffTask = null;
			}
		}
	}
	
	protected final buffTasks autoBuffTask = null;
	
	public static void onLoad()
	{
		if (Config.BUFFER_ENABLED)
		{
			getInstance();
		}
	}
	
	public static Buffer getInstance()
	{
		return SingletonHolder._instance;
	}
	
	private static class SingletonHolder
	{
		protected static final Buffer _instance = new Buffer();
	}
}
